03 新手村基础 Dart 语法 (上)

无论你处于编程的任何阶段,都应该铭记一点:

对于计算机编程而言,最重要的是 维护数据 的变化。

比如你支付宝中的账户金额;你微信聊天记录的内容;你游戏中的资源装备;你随手拍的一张照片、录制的一段意义非凡的视频,都是数据在计算机中的不同表现形式。作为编程者要通过代码逻辑,保证数据在 用户与应用交互 过程中的准确性,来完成功能需求。

本教程只会介绍最最基础的 Dart 语法,能支撑后续教程的内容即可。无论是自然语言,还是编程语言,语法的学习都是非常枯燥的。对于新手而言,不必任何细节都面面俱到,正所谓贪多嚼不烂。学习应该循序渐进、细水长流,先自己能站立了,再想跑的事。如果已经有 Dart 基础的朋友,本章简单过一下就行了,也可能温故而知新哦 ~


一、基础数据类型

先思考一下,在现实世界中,我们如何表示不同类别的事物? 其实很简单,就是根据事物的特征进行划分,然后 取名字 进行表示。当这种表示形式在全人类中达到共识,就会成为一个认知标准,比如下面是一只猫:

image.png

而提到猫,自然就会想到它体型不大,有两个眼睛,两个耳朵,四个腿,脸上有胡须,这就是类型的特征。而 这个名字本身只是一个标志,用于对一类事物的指代。虽然不同的地域可能有不同的叫法,但重要的其实不是它叫什么,而是它是什么,能干什么。


在编程中,数据类型也是类似,它的存在是为了解决:

如何在代码中 表示数据种类 的问题。

解决方法也是一样: 取名字 —- 通过名称区分不同类型数据的特点。在任何编程语言中有 内置基础类型 以供使用,下面来简单看一下 Dart 中基础数据类型变量的定义。到这里,先简单认识一下四种数据类型: 数字、文字是人们日常生活在接触最多的数据,对于任何编程语言来说都是至关重要的。布尔型本身非常简单,只能表示真或假,对逻辑判断有着重要的意义。

image.png


1. 数字类型

在日常生活在,数字 是非常必要的,它可以描述数量,也可以进行数学运算。在 Dart 语言中,数字有两大类型: 整型(整数) int 和浮点型(小数) double 。定义变量的语法是 类型名 变量名 = 值 ,下面代码中定义了两个变量:

void main(){
  // 整型 age,表示年龄的数值
  int age = 2;
  // 浮点型 weight,表示体重的数值
  double weight = 4.5;
}

在这里学习 Dart 基础知识时,可以在项目的 test 文件夹里,编辑代码做测试。点击三角可以运行代码:

image.png


现在通过一个小练习,认识一下代码如何进行简单的逻辑运算:

定义三个浮点型变量 a,b,c,值分别是 2.3, 4.5, 2.5 ; 通过代码计算出平均值 avg ;并将结果输出到控制台。

代码如下,数字量之间可以进行四则运算,求平均值就是将数字相加,除以个数。通过 print 函数可以在控制台输出量的信息:

void main(){
  double a = 2.3;
  double b = 4.5;
  double c = 2.5;

  double avg = (a+b+c)/3;
  print(avg);
}

运行后,可以看到计算的结果。比如 a,b,c 变量分别代表三只小猫的体重,那这段代码就是:解决获得平均体重问题的一个方案。代码本身的意义在于 解决问题, 脱离问题需求而写代码,就像是在和空气斗智斗勇。

image.png


2. 字符串类型

在日常生活中,除了数字之外,最重要的就是 文字,在编程中称之为 字符串。 文字可以为我们提供信息,如何通过代码来表示和操作它,是一件非常重要的事。 在 Dart 中,通过 String 类型表示字符串,字符串值可以通过单引号 'str' 或 双引号 "str" 进行包裹,一般情况下,两者没有太大的区别,用自己喜欢的即可。字符串直接可以通过 + 号进行连接,如下所示:

void main() {
  String hello1 = 'Hello, World!';
  String hello2 = "Hello, Flutter!";
  print(hello1 + hello2);
}

---->[输出结果]----
Hello, World!Hello, Flutter!

通过 $变量名 可以在字符串内插入变量值。如下所示,将计算平均值的输出表述进行了完善:

void main() {
  double a = 2.3;
  double b = 4.5;
  double c = 2.5;

  double avg = (a + b + c) / 3;
  String output = '$a,$b,$c 的平均值是$avg';
  print(output);
}

---->[输出结果]----
2.3,4.5,2.5 的平均值是3.1 

另外,也可以通过 ${表达式} 来嵌入表达式,比如:

 String output = '$a,$b,$c 的平均值是${(a + b + c) / 3}';

也可以理解为变量是特殊的表达式,在插入时可以省略 {}。对于字符串来说,先了解如何定义和使用就行了,以后如有需要,可以在其他资料中系统学习。


3. 布尔型 bool

布尔型用于表示真假,其中只有 truefalse 两个值,一般用于判断的标识。布尔值可以直接书写,可以通过一些运算得到。比如数字的比较、布尔值的逻辑运算等。

void main() {
  // 直接赋值
  bool enable = true;
  double height = 1.18;
  // 布尔值可以通过运算获得
  bool free = height < 1.2;
}

二、运算符

运算符可以和值进行连接,进行特定运算,产出结果;比如加减乘除,大小比较,逻辑运算等。可以说运算符是代码逻辑的半壁江山。对于初学者而言,先掌握下面的三类运算符:

image.png


1. 算数运算符

算数运算符作为数学的基础运算,从小就陪伴着我们,大家应该不会感到陌生。它连接两个数字进行运算,返回数值结果:

image.png

void main() {
  print(1 + 2);//3    加
  print(1 - 2);//-1   减
  print(1 * 2);//2    乘
  print(1 / 2);//0.5  除
  print(10 % 3);//1   余
  print(10 ~/ 3);//3  商
}

2.比较运算符

比较运算符,也是日常生活中极为常见的。它连接两个值进行运算,返回比较的 bool 值结果 :

image.png

void main() {
  print(1 > 2); //false   大于
  print(1 < 2); //true    小于
  print(1 == 2); //false  等于
  print(1 != 2); //true   不等
  print(10 >= 3); //true  大于等于
  print(10 <= 3); //false 小于等于
}

3.逻辑运算符

逻辑运算符,用于连接 bool 值进行运算,返回 bool 值。 理解起来也很简单,&& 表示两个 bool 值都为 true,才返回 true ; || 表示两个 bool 值有一个是 true,就返回 true ; ! 之后连接一个 bool 值,返回与之相反的值。

image.png

如下代码所示,openfree 是两个 bool 值,表示条件。通过 && 运算得到的 bool 值表示需要同时满足这两个条件,即 免费进入 需要公园开放,并且可以免费进入:

void main() {
  // 公园是否开放
  bool open = true;
  // 是否免费
  bool free = false;

  // 公园是否免费进入
  bool freeEnter = open && free;
}

三、流程控制

如果是运算符是代码逻辑的半壁江山,那流程控制 就是另外一半。流程控制可以分为 条件控制循环控制;其中:

条件控制 可以通过逻辑判断的语法规则,执行特定的分支代码块。 循环控制 可以让某个代码块执行若干次,直到符合某些条件节点结束。


1. 条件流程 : if - else

如果怎么样,就做什么,这种选择执行的场景,在日常生活中非常常见。而 if - else 就是处理这种选择分支的语法。 if 之后的 () 中填入布尔值,用于逻辑判断;条件成立时,执行 if 代码块;条件不成立,执行 else 代码块。如下代码根据 free 布尔值,打印是否可以免费入园:

void main() {
  double height = 1.18;
  // 布尔值可以通过运算获得
  bool free = height < 1.2;
  if(free){
    print("可免费入园");
  }else{
    print("请购买门票");
  }
}

2. 条件控制 : switch - case

if - else 只能对 bool 值进行逻辑判断,进行分支处理。某些场景中需要对更多类型的值进行判断,这时就可以使用 switch - case 进行分支处理。 如下代码所示,根据字母等级,打印对应的评级信息,其中 switch 后面的括号在是校验值, case 之后是同类型的值,当 case 后的值和校验值一致时,会触发其下的分支逻辑。 default 分支用于处理无法匹配的场合。

void main() {
  String mark = 'A';
  switch (mark) {
    case 'A':
      print("优秀");
      break;
    case 'B':
      print("良好");
      break;
    case 'C':
      print("普通");
      break;
    case 'D':
      print("较差");
      break;
    case 'E':
      print("极差");
      break;
    default:
      print("未知等级");
  }
}

3. 循环流程 - for 循环

有些情况下,我们需要不断执行某一段逻辑(循环体),直到条件完成(循环条件),这就是循环控制。 for 循环中,() 里有三个表达式,通过 ; 隔开,

第一个表达式是进入循环之前执行的语句,在循环过程中不会再次执行; 第二个表达式是循环条件,每次循环体执行完毕,都会校验一次。当条件满足时,会执行下次循环。 第三个表达式在每次循环体执行完毕后,都会执行一次。

如下代码的含义就是:在循环开始时定义 i 整数变量,赋值为 0, 在 i < 5 的条件下,执行循环体;每次循环体执行完毕后,让 i 增加 1 。 循环体中对 i 值进行累加,并且打印信息:

void main() {
  int sum = 0;
  for (int i = 0; i < 5; i = i + 1) {
    sum = sum + i;
    print("第 $i 次执行,sum = $sum");
  }
}

---->[输出结果]----
第 0 次执行,sum = 0
第 1 次执行,sum = 1
第 2 次执行,sum = 3
第 3 次执行,sum = 6
第 4 次执行,sum = 10

注: i = i + 1 可以简写为 i += 1, 其他的算数运算符也都有这种简写形式。 另外,表示整数 i 自加 1 ,也可以简写为 i++++i。两者的区别在于对 i 赋值的先后性, 自减 1 同理。如果初学者觉得简写难看懂,可以不用简写。

---->[情况1:i++]---- 
int i = 3; 
int a = i++; //执行赋值后i才自加,故a=3 
print('a=$a,i=$i');//a=3,i=4 

---->[情况2:++i]---- 
int i = 3; 
int a = ++i; //执行赋值前i已经自加,故a=4 
print('a=$a,i=$i');//a=4,i=4

4. 循环流程 - while 循环

for 循环和 while 循环并没有什么本质上的区别,只是形式上的不同,两者可以进行相互转换。下面是类比 for 循环中的三块,将上面的代码转换为 while 循环。

image.png

可能有人会问,既然 while 循环和 for 循环可以相互转化,那为什么不干掉一个呢? 就像菜刀和美工刀虽然都可以完成切割的任务,但不同的场景下会有更适合的工具。 while 只需要关注循环条件,在某些场合下更简洁,语义也很不错; for 循环的固定格式,更便于阅读,可以一眼看出循环的相关信息。


另外还有 do - while 循环,算是 while 的变式。 do 代码块中是循环体,while 后依然是条件。 do - while 循环的最大特点是: 先执行循环体,再校验条件。也就是说,它的循环体必然会被执行一次。

void main() {
  int sum = 0;
  int i = 0;
  do{
    sum += i;
    print("第 $i 次执行,sum = $sum");
    i = i + 1;
  } while (i < 5);
}

根据不同的场景,可以选择不同的形式。 但千万别被形式整的晕头转向,记住一点:它们在本质上并没有区别,都是控制条件,执行循环体。而且它们之间都可以进行转换。


5. 中断控制 - break 和 continue

在循环流程中,除了循环条件可以终止循环,还可以通过其他关键字来中断循环。

break 关键字: 直接跳出循环,让循环终止。 continue 关键字:跳出本次循环,将进入下次循环。

void main() {
  // ---->[break情景]----
  for (int i = 0; i < 10; i++) {
    if (i % 3 == 2) {
      break; //直接跳出循环
    }
    print("i:$i"); //打印了 0,1
  }

  // ---->[continue情景]----
  for (int i = 0; i < 10; i++) {
    if (i % 3 == 2) {
      continue; //跳出本次循环,将进入下次循环
    }
    print("i:$i"); //打印了 0,1,3,4,6,7,9
  }
}

四、函数的定义和使用

函数是一段可以有输入和输出的逻辑单元;通过函数,可以将特定的算法进行封装,简化使用。这里,我们通过一个实际的问题场景来介绍函数:

身体质量指数,是BMI(Body Mass Index)指数,简称体质指数,是国际上常用的衡量人体胖瘦程度以及是否健康的一个标准。

对于一个人来说,只要知道自己的身高和体重,就可以通过公式 : BMI=体重÷身高^2 计算出体质指数。如果每次计算时,都要根据公式计算是非常麻烦的。

void main() {
  double toly = 1.8 / (70 * 70);
  double ls = 1.79 / (65 * 65);
  double wy = 1.69 / (50 * 50);
}

另外,如果某天体质指数 的计算公式改变了,那散落在代码中各处的直接计算都需要修改,这无疑是非常麻烦的。 函数的价值就在于封装算法,下面来体会一下函数的价值。


1. 函数的简单定义

函数可以有若干个输入值,和一个输出值。比如对于计算体质指数 来说,有身高 height 和体重 weight 两个小数。输出是 体质指数 的具体值。如下所示,定义 bmi 函数:函数名前是返回值类型;之后的括号中是参数列表,通过 , 号隔开,每个参数由 参数类型 参数名 构成;{} 中是函数的具体算法逻辑:

double bmi(double height, double wight) {
  // 具体算法
  double result = wight / (height * height);
  return result;
}

需要获取一个人的 体质指数 ,只要调用 bmi 函数,传入身高和体重即可。使用者不需要在意公式具体是什么,如果某天公式改变了,只需要修改 bmi 中的算法实现,其他代码都无需改动。所以,函数的价值不仅在于实现算法逻辑,也能为某个功能提供一个公共的接入口,以应对可能改变的风险。

void main() {
  double toly = bmi(1.8, 70);
  double ls = bmi(1.79, 65);
  double wy = bmi(1.69, 50);

  print("===toly:$toly===ls:$ls===wy:$wy===");
}

2. 命名参数

有些时候,函数的参数过多,在调用时需要记清顺序,是比较麻烦的。Dart 中支持命名参数,可以通过参数的名称来传参,不需要在意入参的顺序。通过 {} 包裹命名的参数,其中 required 关键字表示该入参必须传入; 另外,可以用 = 提供参数的默认值,使用者在调用时可以选填:

double bmi({
  required double height,
  double weight = 65,
}) {
  // 具体算法
  double result = weight / (height * height);
  return result;
}

如下所示,在使用时可以通过 weightheight 指定参数,参数的顺序是无所谓的;另外由于 weight 有默认值,在调用时可以不填,计算时会取默认值:

void main() {
  double toly = bmi(weight: 70, height: 1.8);
  double ls = bmi(height: 1.79);
  double wy = bmi(height: 1.69, weight: 50);
}

3. 位置参数

方括号 [] 包围参数列表,位置参数可以给默认值:

double bmi([double height = 1.79, double weight = 65]) {
  // 具体算法
  double result = weight / (height * height);
  return result;
}

在使用时必须要按照参数顺序传入,它和普通参数列表的区别在于:在调用时,可以省略若干个参数,省略的参数使用默认值:

void main() {
  double toly = bmi(70, 1.8);
  double ls = bmi();
  double wy = bmi(1.69);
}

函数是一个非常有用的语法功能,在一个函数中可以调用其他函数,来组织逻辑。上面的 bmi 函数其实只是将一行代码的运算进行封装,已经显现出了很大的价值,更不用说能完成某些特定功能的复杂函数。但封装也很容易造成只会使用,不懂内部实现逻辑的局面,这在即使好事(不用在意底层逻辑,专注上层使用),也是坏事(底层逻辑有问题,难以自己修复)。

这里给个小作业: 找一个计算公式,通过函数来封装对它的调用


五、本章小结

到这里,对于编程来说最最基础的三个模块: 基本数据类型、运算符、流程控制,就有了基本的认识。这些语法点是代码逻辑实现的基石,对烹饪来说,相当于水、火、面、米、油、盐等最基础的材料,对一顿饭来说不可或缺。

基本的食材可以保证能吃饱,但对于一席盛大的晚宴来说,是远远不够的:酱醋茶酒、鸡鸭鱼肉,是更高等的食物素材。函数、对象可以对代码逻辑进行封装,从而更好地维护代码结构。下一篇,将继续介绍 Dart 基础语法,了解面向对象的相关知识。